


解釋程式碼
這份 edit.blade.php 是一個 Laravel Blade 編輯產品頁面,功能包括:
Blade 是 Laravel 的樣板引擎,{{ }} 用來輸出資料,@if、@foreach、@error 則是 Blade 指令。cite
<!DOCTYPE html>
<html lang="zh-TW">
這表示:
zh-TW。<meta charset="UTF-8">
使用 UTF-8 編碼,因此可以正確顯示中文、日文與表情符號。
<meta name="viewport" content="width=device-width, initial-scale=1.0">
讓頁面適應手機、平板與桌面螢幕。
<meta name="csrf-token" content="{{ csrf_token() }}">
這一行把 Laravel 產生的 CSRF Token 放進 HTML 的 <meta> 標籤,稍後 JavaScript 刪除圖片時會讀取它。
CSRF 是 Laravel 用來防止偽造請求的安全機制;只要是修改或刪除資料的請求,就應該附帶有效 Token。cite
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
這裡載入 jQuery 3.6.0,主要用於後面的 AJAX:
$.ajax({
...
});
如果不想依賴 jQuery,也可以改用原生 fetch()。
<style> 裡面的內容負責整個頁面的外觀。
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
這會:
border-box,讓元素的寬度計算比較容易控制。body {
font-family: 'Microsoft JhengHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 40px 20px;
}
設定:
.container {
max-width: 900px;
margin: 0 auto;
}
.container 最大寬度為 900px,並透過:
margin: 0 auto;
讓內容水平置中。
.back-btn {
display: inline-block;
background: white;
color: #667eea;
...
}
這是「返回產品列表」連結的樣式。
.back-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(255, 255, 255, 0.3);
}
滑鼠移上去時,按鈕會向上移動 2px,並出現陰影。
.form-card {
background: white;
border-radius: 20px;
padding: 40px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
}
白色卡片是整個編輯表單的主要容器,具有:
.form-control {
width: 100%;
padding: 12px 16px;
border: 2px solid #ecf0f1;
border-radius: 10px;
}
所有輸入框與文字區域都會套用這個樣式。
.form-control:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
當使用者點擊輸入框時,邊框變成紫藍色並出現淡淡的外框。
<a href="{{ route('admin.products.card') }}" class="back-btn">
← 返回產品列表
</a>
route('admin.products.card')
會根據 Laravel 中命名為 admin.products.card 的路由產生 URL。
例如路由可能是:
Route::get('/admin/products', [ProductController::class, 'card'])
->name('admin.products.card');
這樣 Blade 就不用直接寫死 /admin/products,未來修改路由時較容易維護。
<span class="product-id-badge">#{{ $product->id }}</span>
假設控制器傳入的 $product->id 是 15,瀏覽器上會顯示:
#15
$product 通常是從資料庫取得的 Eloquent Model,例如:
$product = Product::with('images')->findOrFail($id);
@if ($errors->any())
<div class="alert alert-danger">
<strong>請修正以下錯誤:</strong>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
這段程式的作用是:
@foreach 列出所有錯誤訊息。例如控制器可能有以下驗證:
$request->validate([
'title' => ['required', 'string', 'max:255'],
'price' => ['required', 'integer', 'min:0'],
'quantity' => ['required', 'integer', 'min:0'],
]);
如果使用者沒有填寫產品標題,頁面可能顯示:
請修正以下錯誤:
The title field is required.
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
控制器可以使用:
return redirect()
->back()
->with('success', '產品更新成功');
之後,Blade 會讀取 Session 中的 success 訊息並顯示。
@if($product->images && $product->images->count() > 0)
這裡檢查兩件事:
$product->images 是否存在。這表示 Product Model 很可能有以下關聯:
public function images()
{
return $this->hasMany(ProductImage::class);
}
如果有圖片,就執行:
@foreach($product->images as $image)
逐一讀取每張圖片。
<img src="/show-image.php?path={{ urlencode($image->path) }}" alt="產品圖片">
假設:
$image->path = 'products/abc.jpg';
urlencode() 後會放入網址參數:
/show-image.php?path=products%2Fabc.jpg
show-image.php 應該負責:
使用 urlencode() 可以避免檔案路徑中的特殊字元破壞 URL。
@else
<div class="no-images">
<p>📭 尚無圖片</p>
</div>
@endif
如果產品沒有任何圖片,就顯示「尚無圖片」。
<button class="delete-image" onclick="deleteImage({{ $image->id }})">
×
</button>
假設圖片 ID 是 8,實際產生的 HTML 類似:
<button onclick="deleteImage(8)">×</button>
使用者按下按鈕後,會呼叫:
deleteImage(8);
這個按鈕位於圖片右上角,CSS 使用:
position: absolute;
top: 5px;
right: 5px;
因此會疊在圖片上方。
<form method="POST"
action="{{ route('admin.products.update', $product->id) }}">
表單使用 POST 傳送資料,網址由命名路由產生。
Laravel 的命名路由可能如下:
Route::put('/admin/products/{product}',
[ProductController::class, 'update'])
->name('admin.products.update');
@csrf
這會產生類似以下的隱藏欄位:
<input type="hidden" name="_token" value="...">
它用來驗證請求是否來自合法的網站表單。
@method('PUT')
HTML 表單原生只支援 GET 和 POST,不直接支援 PUT、PATCH 或 DELETE。Laravel 會利用隱藏欄位 _method 來偽裝 HTTP 方法。cite
實際上會產生:
<input type="hidden" name="_method" value="PUT">
因此,雖然表單標示:
method="POST"
Laravel 最後會把它視為:
PUT /admin/products/{id}
<input type="text"
name="title"
class="form-control"
value="{{ old('title', $product->title) }}"
required>
name="title"控制器可以透過:
$request->title
或:
$request->input('title')
取得內容。
old('title', $product->title)這段很重要:
old('title', $product->title)
意思是:
$product->title。例如資料庫標題是:
初級鋼琴課程
使用者修改成:
成人鋼琴入門課程
但其他欄位驗證失敗,重新載入頁面時仍會保留使用者輸入的新版標題。
requiredrequired
這是瀏覽器端的基本驗證,要求標題不可空白。不過後端仍然必須再次驗證,因為瀏覽器驗證可以被繞過。
@error('title')
<p class="error-text">{{ $message }}</p>
@enderror
這表示如果 title 欄位有錯誤,就顯示該欄位的錯誤訊息。
Laravel 的 @error 指令可以快速檢查特定欄位是否有驗證錯誤。cite
其他欄位也使用相同方式:
@error('content')
@error('price')
@error('quantity')
<textarea name="content" class="form-control">
{{ old('content', $product->content) }}
</textarea>
這是多行文字輸入框,適合輸入:
textarea.form-control {
min-height: 120px;
resize: vertical;
}
表示文字區域:
需要注意的是,這個欄位的內容直接放在 <textarea> 開始與結束標籤之間,而不是放在 value 屬性中。
<input type="number"
name="price"
class="form-control"
value="{{ old('price', $product->price) }}"
min="0"
step="1"
required>
這個欄位的設定如下:
| 屬性 | 作用 |
|---|---|
type="number" |
顯示數字輸入欄位 |
name="price" |
欄位名稱為 price |
min="0" |
不允許輸入負數 |
step="1" |
以整數為單位 |
required |
必須填寫 |
不過 min、step 和 required 只是前端限制,後端仍應驗證:
'price' => ['required', 'integer', 'min:0']
<input type="number"
name="quantity"
class="form-control"
value="{{ old('quantity', $product->quantity) }}"
min="0"
required>
這是庫存欄位,允許輸入 0,代表目前沒有庫存。
後端可以使用:
'quantity' => ['required', 'integer', 'min:0']
確保數量是非負整數。
<button type="submit" class="btn-submit">
💾 儲存變更
</button>
按下後,瀏覽器會送出表單,Laravel 將請求交給產品更新控制器,例如:
public function update(Request $request, Product $product)
{
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'content' => ['nullable', 'string'],
'price' => ['required', 'integer', 'min:0'],
'quantity' => ['required', 'integer', 'min:0'],
]);
$product->update($validated);
return redirect()
->route('admin.products.edit', $product)
->with('success', '產品更新成功');
}
deleteImage() JavaScript 函式function deleteImage(imageId) {
這個函式接收圖片 ID。
例如:
deleteImage(8);
表示要刪除 ID 為 8 的圖片。
if (!confirm('確定要刪除這張圖片嗎?')) {
return;
}
confirm() 會顯示確認視窗。
return,停止函式。這可以避免使用者誤刪圖片。
$.ajax({
url: '/admin/products/images/' + imageId,
method: 'DELETE',
假設 imageId 是 8,請求網址會變成:
/admin/products/images/8
HTTP 方法是:
DELETE
Laravel 路由可能是:
Route::delete(
'/admin/products/images/{image}',
[ProductImageController::class, 'destroy']
)->name('admin.products.images.destroy');
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
這段程式:
<meta name="csrf-token">。content。X-CSRF-TOKEN HTTP Header。如果忘記傳送 Token,Laravel 常會回傳 419 Page Expired。cite
success: function(response) {
location.reload();
}
刪除成功後重新載入整個頁面,讓已刪除的圖片不再顯示。
error: function(xhr) {
alert('刪除失敗,請稍後再試');
}
如果伺服器回傳錯誤,就顯示提示訊息。
實務上可以進一步查看錯誤狀態:
error: function(xhr) {
console.error(xhr.responseText);
alert('刪除失敗,請稍後再試');
}
這個頁面的完整流程如下:
edit.blade.php。$product 資料填入 HTML。POST 傳送,並透過 @method('PUT') 告訴 Laravel 這是更新請求。×。DELETE 請求。目前寫法是:
url: '/admin/products/images/' + imageId
如果網站部署在子目錄,或未來修改路由,可能造成網址錯誤。可以將路由網址放進 HTML:
<button
type="button"
class="delete-image"
data-url="{{ route('admin.products.images.destroy', $image->id) }}"
onclick="deleteImage(this.dataset.url)">
×
</button>
JavaScript 改成:
function deleteImage(deleteUrl) {
if (!confirm('確定要刪除這張圖片嗎?')) {
return;
}
$.ajax({
url: deleteUrl,
method: 'DELETE',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
success: function() {
location.reload();
},
error: function(xhr) {
console.error(xhr.responseText);
alert('刪除失敗,請稍後再試');
}
});
}
type="button"目前按鈕寫成:
<button class="delete-image">
如果它位於表單內,瀏覽器預設可能把它視為 submit。雖然目前圖片區塊位於 <form> 外部,但建議仍明確寫成:
<button type="button" class="delete-image">
/show-image.php目前圖片透過:
/show-image.php?path=...
輸出。更 Laravel 化的作法是建立圖片路由:
Route::get('/images/{image}', [ImageController::class, 'show'])
->name('images.show');
Blade:
<img src="{{ route('images.show', $image) }}" alt="產品圖片">
這樣可以將權限驗證、檔案存在檢查與路徑安全性集中放在 Controller。
如果 show-image.php 直接使用使用者傳入的 path,要特別防止目錄穿越攻擊,例如:
../../.env
後端應限制圖片只能從指定目錄讀取,並檢查正規化後的路徑是否仍位於安全目錄內。
目前刪除成功後:
location.reload();
這個方式簡單可靠,但使用者體驗不是最理想。也可以在成功後直接移除圖片元素:
success: function() {
$('#image-' + imageId).remove();
}
需要配合 HTML:
<div id="image-{{ $image->id }}" class="image-item">
因為只能截圖
所以~
提供鋼琴留言版
https://laihao.vaserver.com/gbook
可以實際測試
也歡迎大家一起來學習喔
https://www.pressplay.cc/project/9D489B43D08BD198FDEB03ECDEFAC161/about